package factories;

import fileHandling.ProfileHandler;
import gameStates.absGamesStates.AbsIngameState;

import java.util.ArrayList;
import java.util.List;

import logic.common.Squad;
import logic.common.game.Game;
import logic.common.player.Player;
import logic.common.team.Fraction;
import logic.common.team.Team;
import logic.nodes.collision.visibility.SightController;
import logic.nodes.lod.blocks.LeafBlock;
import logic.ships.hunter.Hunter;
import logic.ships.mothership.MotherShip;

import main.GameCycle;
import main.InitGame;
import map.Map;
import map.infos.MapInfos;
import map.spawnStation.SpawnStation;

/**
 * Provides methods for Game-Creation.
 * @author Wasserleiche
 */
public class GameFactory {
	
	public static int botIndex;
	
	public static final int BLOCK_CONTROLLERS = 8;
	
	/**
	 * Creates a new {@link Game} with the given information and returns it. Only the {@link Team}s 
	 * will be initialized. 
	 * @param map The {@link Map} the game will work with.
	 * @param respawn The respawn-Interval of the game.
	 * @param countDown The countdown at the {@link Game}'s end.
	 * @return
	 */
	public static Game createGame(Map map, AbsIngameState ingameState, GameCycle gameCycle) {
		if(map == null) return null;
		
		Fraction fA = map.getFractionA();
		Team teamA = getTeam(fA, map, ingameState);
		if(teamA == null) return null;
		
		Fraction fB = map.getFractionB();
		Team teamB = getTeam(fB, map, ingameState);
		if(teamB == null) return null;
		
		teamA.setEnemyTeam(teamB);
		teamB.setEnemyTeam(teamA);
		
		Game game = new Game(map, teamA, teamB, gameCycle);
		ingameState.setGame(game);
		
		Player player = new Player(ProfileHandler.getSelectedProfile(), true, teamA, teamB);
		if(!InitGame.get().isServer()) player.addGameTimeListener(ingameState);
		ingameState.setPlayer(player);
		
		if(game.getGameSetup().hasMotherShips()) {
			createNewMotherShip(teamA, map);
			createNewMotherShip(teamB, map);
		}
		
		game.initEndConditions();
		
		ingameState.getHUDState().init();
		
		game.initSpawnStations(ingameState);
		
		game.addStandardMissions();
		
		initBlockControllers(ingameState, teamA, teamB);
		
		return game;
	}
	
	private static void initBlockControllers(AbsIngameState ingameState, Team teamA, Team teamB) {
		int leafBlocks = LeafBlock.allLeafBlocks.size() - 1;
		assert(leafBlocks % 2 == 0);
		assert(leafBlocks % BLOCK_CONTROLLERS == 0);
		int blockPerController = leafBlocks / BLOCK_CONTROLLERS;
		assert(leafBlocks % blockPerController == 0);
		
		int blockIndex = 0;
		for(int i = 0; i < BLOCK_CONTROLLERS; i++) {
			List<LeafBlock> blocks = new ArrayList<LeafBlock>();
			
			int nextUpperIndex = blockIndex + blockPerController;
			for(; blockIndex < nextUpperIndex; blockIndex++) {
				blocks.add(LeafBlock.allLeafBlocks.get(blockIndex));
			}
			
			SightController c = new SightController(blocks, teamA, teamB);
			ingameState.getThreadPool().registerController(c);
		}
	}
	
	public static void initSpawnStations(Game game) {
		List<SpawnStation> stations = game.getMap().getSpawnStations();
		
		for(SpawnStation station : stations) {
			station.setAvailableTeams(game.getTeamA(), game.getTeamB());
		}
	}
	
	/**
	 * Sets up all AI-{@link Player}s of the given {@link Team}.
	 * @param team The {@link Team} whose Bots has to be initialized.
	 * @param enemyTeam The enemy {@link Team}.
	 * @param maxTeamPlayers The maximum number of {@link Player}s of the given {@link Team}.
	 */
	public static void setUpPilotBots(Team team, Team enemyTeam, int maxTeamPlayers) {
		for(int i = 0; i < maxTeamPlayers && team.getAllPlayers().size() < maxTeamPlayers; i++) {
			Player bot = new Player("Bot" + botIndex, false, team, enemyTeam);
			botIndex++;
			addToSquad(bot);
			
			Hunter newHunter = HunterFactory.createRandomHunter(bot);
			if(newHunter == null) return;
			bot.changeHunter(newHunter);
		}
		
		team.initTeamAIController();
	}
	
	private static void addToSquad(Player bot) {
		Team team = bot.getTeam();
		Squad squad = team.getNextNonFullSquad();
		bot.joinSquad(squad);
	}
	
	/**
	 * Creates a new {@link Team}. A new {@link MotherShip} will also be set up.
	 * @param fraction The {@link Fraction} of the {@link Team}.
	 * @param map The {@link Map} where the {@link MotherShip}'s spawn can be found.
	 * @return A new {@link Team} of the given {@link Fraction}.
	 */
	public static Team getTeam(Fraction fraction, Map map, AbsIngameState ingameState) {
		MapInfos infos = map.getMatchInfos();
		int pilotMoney = infos.getPilotMoney();
		return new Team(fraction, map.getTeamInfos(fraction), pilotMoney, ingameState);
	}
	
	private static void createNewMotherShip(Team team, Map map) {
		MotherShip motherShip = MotherShipFactory.createMotherShip(team);
		if(motherShip != null) 
			team.setMotherShip(motherShip, map.getTeamInfos(team.getFraction()).getMotherShipSpawn());
	}
	
	public static void finishMatchState(Game game, AbsIngameState ingameState) {
		game.setupGame(ingameState);
		game.getGameSetup().spawnStationsDone();
		game.startGame();
	}
}